# -*- coding: utf-8 -*-
""" Data structures """


__author__ = "Kamil Gołuński"
__date__ = "$2010-03-01 15:48:56$"

from munkres import Munkres
import copy
import math
import random
from time import clock
import wx


class Point:
    """  """
    def __init__(self, cords):
        self.cords = cords

    def distance(self, to_point, minmatch=None):
        return (self.cords[0]-to_point.cords[0]) ** 2 + (self.cords[1]-to_point.cords[1]) ** 2
    """
        def __add__(self, second):
            return Point([self.cords[0] + (second.cords[0] if type(self) == type(second) else  second),
                         self.cords[1] + (second.cords[1] if type(self) == type(second) else  second)
                         ])

        def __sub__(self, second):
             return Point([self.cords[0]-(second.cords[0] if type(self) == type(second) else  second),
                          self.cords[1]-(second.cords[1] if type(self) == type(second) else second)
                          ])

        def __mul__(self, second):
             return Point([self.cords[0] * (second.cords[0] if type(self) == type(second) else second),
                          self.cords[1] * (second.cords[1] if type(self) == type(second) else second)
                          ])

    """
class Shape:
    """  """
    def __init__(self):
        self.shape_points = []
        self.image = None
        #self.loadShape(image_path)

    @classmethod
    def fromPath(cls, image_path, shape_color=(255, 255, 255)):
        obj = cls()
        obj.loadShape(image_path, shape_color)

        return obj

    @classmethod
    def fromVector(cls, image_vector):
        obj = cls()
        obj.loadVShape(image_vector)

        return obj

    def loadShape(self, image_path, shape_color):
        """ Reads shape points (in given color) from file """
        points = []
        bmp = wx.Bitmap(image_path)
        image = bmp.ConvertToImage()
        for x in range(image.GetWidth()):
            for y in range(image.GetHeight()):
                if (image.GetRed(x, y), image.GetGreen(x, y), image.GetBlue(x, y)) == shape_color:
                    points.append(Point([x, y]))
        self.shape_points = points
        self.image = image
        return [self.shape_points, image]

    def loadVShape(self, image_vector):
        self.shape_points = copy.deepcopy(image_vector)
    
    def pickRandomPoint(self):
        try:
            picked = random.choice(self.shape_points)
        except IndexError:
            print 'Points list is empty!'
        else:
            return picked

class CodeBookPoint(Point):
    def __init__(self, point):
        self.cords = copy.deepcopy(point)
    """
        def __add__(self, second):
            return CodeBookPoint([self.cords[0] + (second.cords[0] if type(self) == type(second) else  second),
                         self.cords[1] + (second.cords[1] if type(self) == type(second) else  second)
                         ])

        def __sub__(self, second):
             return CodeBookPoint([self.cords[0]-(second.cords[0] if type(self) == type(second) else  second),
                          self.cords[1]-(second.cords[1] if type(self) == type(second) else second)
                          ])

        def __mul__(self, second):
             return CodeBookPoint([self.cords[0] * (second.cords[0] if type(self) == type(second) else second),
                          self.cords[1] * (second.cords[1] if type(self) == type(second) else second)
                          ])
    """
    def move(self, to_point, f, eta, darray):
        for cord in range(len(self.cords)):
            self.cords[cord] = self.cords[cord] + eta * (to_point.cords[cord] - self.cords[cord]) * f

class CodeBookShape(CodeBookPoint):

    def calculateDistances(self, points, distances):
        """
            Calculate distances beetwen all points in given set and all points in code-book shape.
            Returns a list of lists of distances in matrix-orientation
        """
        
        for point in range(len(points)):
            for cord in range(len(self.cords)):
                distances[cord][point] = int(points[point].distance(self.cords[cord]) * 1000)
                #distances.append((cord, point + (len(points) if continous else 0), -int(points[point].distance(self.cords[cord]) * 1000)))

        return distances


    def move(self, to_point, f, eta, minmatch):
        cords = self.cords
        #graph = self.calculateDistances(to_point)
        #graph = []
        #for point in range(len(to_point)):
        #    for cord in range(len(self.cords)):
        #        graph.append((cord, point + len(to_point),-int(darray[str(int(to_point[point].cords[0]))+','+str(int(to_point[point].cords[1]))][int(self.cords[cord].cords[0])][int(self.cords[cord].cords[1])]) * 1000 ))
        #minmatch = mwmatching.maxWeightMatching(graph, maxcardinality=True)
        #for i in range(len(to_point)):
        #    print self.cords[i], to_point[minmatch[i]-len(to_point)] , self.cords[i], f
        #    self.cords[i] = self.cords[i] + eta * (to_point[minmatch[i]-len(to_point)] - self.cords[i]) # * f
        for point in range(len(to_point)):
            for cord in range(len(to_point[point].cords)):
                cords[point].cords[cord] = cords[point].cords[cord] + eta * (to_point[minmatch[point]-len(to_point)].cords[cord] - cords[point].cords[cord]) * f
        self.cords = cords

    def distance(self, to_point, minmatch):
        d = 0
        for point in range(len(to_point)):
            d += self.cords[point].distance(to_point[minmatch[point]-len(to_point)])
        return d

    def getPoints(self):
        return self.cords

class CodeBookVector():
    def __init__(self, shape, lenght, type):
        self.distance_array = {}
        self.minmatch = [0]*lenght
        self.shape = shape
        self.cbvector = [(CodeBookPoint(s.cords[:]) if type == 'P' else CodeBookShape(s)) for s in random.sample(shape.shape_points, lenght)]

    def initFinalMatchingMatrix(self, src_len):
        self.final_matching_matrix = []
        #self.final_matching_matrix = [ [0 for j in range(len(self.cbvector)) ] for i in range(src_len)]
    '''
        def minmatching(self, point):
            cbvector = self.cbvector
            minmatch = []
            for v in range(len(cbvector)):
                minmatch.append(mwmatching.maxWeightMatching(cbvector[v].calculateDistances(point), maxcardinality=True))
            self.minmatch = minmatch
    '''
    def munkresMinMatching(self,point,saveDist = False):
        cbvector = self.cbvector
        vmatrix = []
        distances = [ [0 for i in range(len(point)) ] for j in range(len(point)) ]
        #matrix = [ [0 for i in range(len(point)) ] for j in range(len(point)) ]
        for v in range(len(cbvector)):
            matrix = cbvector[v].calculateDistances(point,distances)
            vmatrix.append(copy.deepcopy(matrix))
        m = Munkres()
        indexes = [ m.compute(vm) for vm in vmatrix ]
        minmatch = [ [0 for i in range(len(point)) ] for j in range(len(cbvector)) ]
        tmp_matrix = []
        for ind in range(len(indexes)):
            if saveDist:
                tmp_matrix.append( sum([vmatrix[ind][i][j] for i,j in indexes[ind]]))
                #tmp_matrix.append( sum([ matrix[i][j] for i,j in indexes[ind] ]))
            for i in range(len(indexes[ind])):
                minmatch[ind][indexes[ind][i][0]]=indexes[ind][i][1]+len(point)
        self.minmatch = minmatch
        if saveDist:
            self.final_matching_matrix.append(tmp_matrix)

    def move(self, to_point, f, eta):
        cbvector = self.cbvector
        minmatch = self.minmatch
        for v in range(len(cbvector)):
            cbvector[v].move(to_point, f[v], eta, minmatch[v])

    def calcF(self, point, Beta, p = False):
        #point musi byc lista punktow lub punktem
        minmatch = self.minmatch
        Sum = []
        cbvector = self.cbvector
        for j in range(len(cbvector)):
            tmp_sum = cbvector[j].distance(point,minmatch[j])
            Sum.append(math.exp(-Beta * tmp_sum))
        sum_ = sum(Sum)
        #if sum_ == 0 : print 'sum_ Sum ',sum_, Sum
        f = [Sum[i] / sum_ for i in range(len(cbvector))]
        return f


    def genDistArray(self, width, height):
        distance_array = {}
        for shape in self.getPoints(self.shape.shape_points):
            for point in shape:
                dist = []
                for w in range(width):
                    dist.append([])
                    for h in range(height):
                        dist[-1].append((point[0]-w) ** 2 + (point[1]-h) ** 2)
                #print str(int(point[0])) +','+ str(int(point[1]))
                distance_array[str(int(point[0])) + ',' + str(int(point[1]))] = dist
        #print distance_array
        self.distance_array = distance_array

    def getPoints(self, source=None):
        if source == None:
            source = self.cbvector
        points  = []
        #print source
        for cbv in source:
            if not isinstance(cbv, CodeBookShape):
                if isinstance(cbv, list):
                    points.append(self.getPoints(cbv))
                else:
                    points.append(cbv.cords)
            else:
                points.append([])
                for cord in cbv.cords:
                    points[-1].append(cord.cords)
        return points